本章节内容基于《Programming in Scala》 2010

基础定义

变量定义以 var / val 开始

Scala 有两种变量: varval:

  • var: 类似 final 变量,初始化后不可再被赋值
  • val: 普通变量
val msg : String = " Hello, world! "

这里也可以通过类型推断来避免说明数据类型

函数定义以 def 开始

def max(x:Int, y: Int) : Int = {
    if ( x > y ) x 
    else y
}

所有方法的参数都是val

如果没有显示的返回return语句,方法返回最后一次计算得到的值。以鼓励简化方法,采用小方法,而不是复杂的大方法。

符号可以做方法名

可以使用更为灵活的调用符号方法

0 to 10 ; 
0.to(10); //it is the same

即0这个字面量对象调用了Int类的to方法

注释方法

// 是行注释;/* */ 是段落注释


集合类型 (Collection)

数组 Array

脚本的命令行参数,存储在 args 的数组中。数组的定位用(),而非[]。即只有args(0),而没有args[0]

列表 List

数组(array)是可变的同类对象序列,Array[String] 实例化后长度固定,但元素值都可变。 列表(list)是不可变的同类对象序列,一旦创建不可改变。

集合 Set

set 较 list, 不能包含重复的元素。 Set 是一个trait,而不是class,进而又分化成了可变(mutable)和不可变(immutable)两类特质。

有两种HashSet类,各扩展了可变和不可变两种特质。两类不同的HashSet的+方法,其表现就不一样。 可变的把新增元素加入自身,不可变的返回一个新集合

映射 map

下列代码:

1 -> "example"  // also can be (1).->("example")

返回[Int, String]的Tuple。在Scala中,任意对象都能用户-> 方法,返回包含键值对的二元组,这类机制为隐式转换


理解函数式编程

区别指令式编程(传统的)和函数式编程的一个关键点在于代码中,是否包含 var。 函数式编程风格尝试着不用任何var来编程。

指令式:

def printArgs(args: Array[String]) : Unit = {
    var i = 0
    while (i < args.length){
        println(args(i))
        i += 1
    }
}

函数式:

def printArgs(args: Array[String]) : Unit = {
    for (arg <- args){
        println(arg)
    }
}

上述printArgs有副作用,即打印到标准输出,因此不是纯函数式的。 若一个函数返回Unit,意味着函数不返回任何有用的值,因此它的作用只通过副作用来体现。

def formatArgs(args: Array[String]) = args.mkString("\n")

上述formatArgs函数,就没有任何副作用了。


对象类和对象

存在 public 和 private 两类修饰符,默认为public

可以构建抽象类,即在class前加上 abstract 修饰符即可。抽象类中的方法定义,只要没有定义,即为抽象方法,不需有抽象修饰符。 对应地,抽象类不可以被实例化。

Java 包括四个命名空间:字段、方法、类型、包。这意味着,一个字段和一个方法可以拥有相同的名称。 Scala只有两个命名空间:值(包括字段、方法、包、单例对象)和类型(类和特质)。因此,如果把一个方法和一个字段设成同名,则会造成编译错误。

程序执行方式

构建自己的main方法

当一个singleton单例对象中包含main方法(带合适签名?),就可以用作是程序的入口点

通常情况下,单个Scala源代码可视为一个脚本,利用scala命令,可以直接执行其中的表达式。若需要构建完整的程序并执行,需要利用scalac命令对源代码进行编译。或者利用fsc建立一个后台的scala编译服务器(fast Scala compiler) 编译完成后,利用scala命令执行包含main方法的独立对象名,以及其参数。

扩展Application特质

scala.Application特质混入单例对象,无需定义main方法,而是该对象定义中的所有语句,即包含在它的primary constructor主构造中的所有代码都会被执行。

特殊的数据类型和操作符

跨行的字符串

引入”“” 来作为大段包含转义字符或若干行的字符串的开始和结束。 如果需要避免隔行间的空格,需要在每行开始处放置一个管道符。

println("""|Welcome to Ultamix 3000
     |Type "Help" for help""")

也说要用stripMargin方法,但貌似不用也可以。

符号字面量

没见过类似的概念,用于表示一个特定的符号,而非字符串。

scala> val s = 'aSymbol
s: Symbol = 'aSymbol

scala> s.name
res3: String = aSymbol

函数字面量

即匿名的函数,可以包含在一个函数内,与引用函数共享参数。在运行过程中,函数字面量实例化为函数值(function value)。下面是几种在过滤集合时,应用函数字面量的例子。注意在使用_占位符时,每个参数只能出现一次,而不能反复出现。

val someNumbers = List(-11, -10, 0, 5, 10)

scala > someNumbers.filter( (x : Int) => x > 0)

scala > someNumbers.filter( x => x > 0)

scala > someNumbers.filter( _ => _ > 0)

控制结构

if 这样的控制结构是表达式,可以产生值。而while这样的循环就不是表达式,因为其结果类型是Unit,其所有效果都是依靠其副作用来完成的。

for 是一个表达式,因为它可以利用yield关键字,生成一个结果循环体集合对象。

高阶函数

柯里化

以下述代码为例:

scala > def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Int

scala > curriedSum(1)(2)
res1: Int = 3

scala > scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)Int => Int

scala> val second = first(1)
second: Int => Int = <function1>

scala> second(2)
res3: Int = 3

柯里化函数curriedSum实际就是后续两个函数firstsecond的结合体。 在调用过程中,首先准备一个first函数,它接受参数x,然后返回第二个函数。 第二个函数接受(y:Int)为参数,输出x+y的来作为结果。

此例中,在second定义时通过调用first函数,并赋予其参数x为1,从而返回第二个接受y为参数的函数。当调用second时,给参数y赋值为2,进而得到结果值3。

借贷模式

以下述代码为例:

def withPrintWriter(file: File, op: PrinterWriter => Unit){
    val writer = new PrintWriter(file)
    try{
        op(writer)
    } finally {
        writer.close()
    }
}

withPrintWriter(
        new File("date.txt"),
        writer => writer.println(new java.util.Date)
    )

此处,对于资源writer 的管理,都是交给withPrintWriter函数本身来完成的,而需要写入内容的业务操作,则作为一个函数值,传递给withPrintWriter,借取writer来执行。执行结束后,由withPrintWriter函数再来关闭资源。

借贷模式可以配合柯里化一起使用。由于Scala允许在只有一个参数的时候,用{},而不是()来引用参数。因此,可能将witherPrintWriter的两个参数利用柯里化而转换成两个独立的参数,这样第二个参数就可以使用{}括起,而使得函数参数看起来像是过程语句了。具体见书 P110

results matching ""

    No results matching ""